﻿using System.Text;
using System.Text.RegularExpressions;
using Microsoft.FluentUI.AspNetCore.Components.AssetsGenerator.Model;

namespace Microsoft.FluentUI.AspNetCore.Components.AssetsGenerator;

internal partial class IconsCodeGenerator
{
    /// <summary>
    /// Initializes a new instance of the <see cref="IconsCodeGenerator"/> class.
    /// </summary>
    /// <param name="configuration"></param>
    public IconsCodeGenerator(Configuration configuration)
    {
        Configuration = configuration;
        Logger = (message) => { };
    }

    /// <summary>
    /// Gets or sets the logger.
    /// </summary>
    public Action<string> Logger { get; init; }

    /// <summary>
    /// Gets the configuration.
    /// </summary>
    private Configuration Configuration { get; }

    /// <summary>
    /// Reads all SVG files in the assets folder.
    /// </summary>
    /// <returns></returns>
    public IEnumerable<Icon> ReadAllAssets()
    {
        const string searchPattern = "*.svg";
        var icons = new Dictionary<string, Icon>();

        Logger.Invoke($"Reading all SVG files in {Configuration.AssetsFolder}.");
        var allFiles = Configuration.AssetsFolder.GetFiles(searchPattern, SearchOption.AllDirectories);

        foreach (var file in allFiles)
        {
            var newIcon = new Icon(file);
            var key = newIcon.Key.ToLower();

            if (!icons.ContainsKey(key))
            {
                icons.Add(newIcon.Key.ToLower(), newIcon);
            }
        }

        return icons.Values
                    .OrderBy(i => i.Name)
                    .ThenBy(i => i.Size)
                    .ThenBy(i => i.Variant)
                    .ToArray();
    }

    /// <summary>
    /// Generates the main class with all icon info.
    /// </summary>
    /// <param name="icons"></param>
    /// <returns></returns>
    public FileInfo GenerateMainIconsClass(IEnumerable<Icon> icons)
    {
        var file = new FileInfo(Path.Combine(Configuration.TargetFolder.FullName, "Icons.cs"));

        Logger.Invoke($"Generating {file.Name}, containing {icons.Count()} icons,");
        Logger.Invoke($"in the target folder {Configuration.TargetFolder.FullName}.");
        var classContent = GenerateMainClass(icons);

        File.WriteAllText(file.FullName, classContent);

        return file;
    }

    /// <summary>
    /// Generates one class for the given icons.
    /// </summary>
    /// <param name="icons"></param>
    /// <returns></returns>
    public FileInfo GenerateOneClass(IEnumerable<Icon> icons, string className)
    {
        Logger.Invoke($"Generating {className}, containing {icons.Count()} icons.");
        var classContent = GenerateOneClass(className, icons.OrderBy(i => i.Name));

        var file = new FileInfo(Path.Combine(Configuration.TargetFolder.FullName, $"{className}.cs"));
        File.WriteAllText(file.FullName, classContent);

        return file;
    }

    /// <summary>
    /// Generates all classes for the given icons.
    /// </summary>
    /// <param name="icons"></param>
    /// <returns></returns>
    public IEnumerable<FileInfo> GenerateClasses(IEnumerable<Icon> icons)
    {
        var generatedFiles = new List<FileInfo>();
        var allSizes = icons.Select(i => i.Size)
                            .Distinct()
                            .OrderBy(i => i);
        var allVariants = icons.Select(i => i.Variant)
                               .Distinct()
                               .OrderBy(i => i);

        // Delete previous files
        foreach (var file in Configuration.TargetFolder.GetFiles("*.*", SearchOption.TopDirectoryOnly))
        {
            bool toDelete = NameParser().IsMatch(file.Name);
            if (toDelete)
            {
                file.Delete();
            }
        }

        // Generate all classes
        foreach (var variant in allVariants)
        {
            foreach (var size in allSizes)
            {
                // CSharp
                var file = new FileInfo(Path.Combine(Configuration.TargetFolder.FullName, $"{variant}{size}.cs"));
                var iconsForSizeAndVariant = icons.Where(i => i.Size == size && i.Variant == variant).OrderBy(i => i.Name);

                Logger.Invoke($"Generating {file.Name}, containing {iconsForSizeAndVariant.Count()} icons.");
                var classContent = GenerateClass(size, variant, iconsForSizeAndVariant);

                File.WriteAllText(file.FullName, classContent);
                generatedFiles.Add(file);
            }
        }

        return generatedFiles;
    }

    /// <summary />
    private string GenerateMainClass(IEnumerable<Icon> icons)
    {
        var builder = new StringBuilder();

        builder.AppendLine("// <auto-generated>");
        builder.AppendLine("//     This code was generated by a tool.");
        builder.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
        builder.AppendLine("//     the code is regenerated.");
        builder.AppendLine("// </auto-generated>");
        builder.AppendLine();
        builder.AppendLine("namespace " + Configuration.Namespace + ";");
        builder.AppendLine();
        builder.AppendLine("/// <summary />");
        builder.AppendLine("[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]");
        builder.AppendLine("public static partial class Icons");
        builder.AppendLine("{");

        // Dictionary
        builder.AppendLine("    /// <summary />");
        builder.AppendLine("    public static IEnumerable<IconInfo> AllIcons");
        builder.AppendLine("    {");
        builder.AppendLine("        get");
        builder.AppendLine("        {");
        foreach (var icon in icons)
        {
            builder.AppendLine($"            yield return new IconInfo {{ Name = \"{icon.Name}\", Variant = IconVariant.{icon.Variant}, Size = IconSize.Size{icon.Size} }};");
        }
        builder.AppendLine("        }");
        builder.AppendLine("    }");
        builder.AppendLine("}");

        return builder.ToString();
    }

    /// <summary />
    private string GenerateClass(int size, string variant, IEnumerable<Icon> icons)
    {
        var builder = new StringBuilder();

        builder.AppendLine("// <auto-generated>");
        builder.AppendLine("//     This code was generated by a tool.");
        builder.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
        builder.AppendLine("//     the code is regenerated.");
        builder.AppendLine("// </auto-generated>");
        builder.AppendLine();
        builder.AppendLine("#pragma warning disable 1591");
        builder.AppendLine();
        builder.AppendLine("namespace " + Configuration.Namespace + ";");
        builder.AppendLine();
        builder.AppendLine("public static partial class Icons");
        builder.AppendLine("{");
        builder.AppendLine("    public static partial class " + variant);
        builder.AppendLine("    {");
        builder.AppendLine("        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]");
        builder.AppendLine("        public static partial class Size" + size);
        builder.AppendLine("        {");

        AddProperties(builder, icons);

        builder.AppendLine("        }");
        builder.AppendLine("    }");
        builder.AppendLine("}");
        builder.AppendLine();
        builder.AppendLine("#pragma warning restore 1591");

        return builder.ToString();
    }

    /// <summary />
    private string GenerateOneClass(string className, IEnumerable<Icon> icons)
    {
        var builder = new StringBuilder();

        var allSizes = icons.Select(i => i.Size)
                            .Distinct()
                            .OrderBy(i => i);
        var allVariants = icons.Select(i => i.Variant)
                               .Distinct()
                               .OrderBy(i => i);

        builder.AppendLine("// <auto-generated>");
        builder.AppendLine("//     This code was generated by a tool.");
        builder.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
        builder.AppendLine("//     the code is regenerated.");
        builder.AppendLine("// </auto-generated>");
        builder.AppendLine();
        builder.AppendLine("#pragma warning disable 1591");
        builder.AppendLine();
        builder.AppendLine("namespace " + Configuration.Namespace + ";");
        builder.AppendLine();
        builder.AppendLine($"internal static partial class {className}");
        builder.AppendLine("{");

        foreach (var variant in allVariants)
        {
            builder.AppendLine("    internal static partial class " + variant);
            builder.AppendLine("    {");

            foreach (var size in allSizes)
            {
                var iconsForSizeAndVariant = icons.Where(i => i.Size == size && i.Variant == variant)
                                                  .OrderBy(i => i.Name);

                if (iconsForSizeAndVariant.Any())
                {
                    builder.AppendLine("        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]");
                    builder.AppendLine("        internal static partial class Size" + size);
                    builder.AppendLine("        {");

                    AddProperties(builder, iconsForSizeAndVariant, indentation: 12);

                    builder.AppendLine("        }");
                }
            }

            builder.AppendLine("    }");
        }

        builder.AppendLine("}");
        builder.AppendLine();
        builder.AppendLine("#pragma warning restore 1591");

        return builder.ToString();
    }

    private static void AddProperties(StringBuilder builder, IEnumerable<Icon> icons, int indentation = 12)
    {
        string indentationString = new(' ', indentation);

        // Properties
        foreach (var icon in icons)
        {
            var svgContent = icon.GetContent(removeSvgRoot: true)
                                 .Replace("\"", "\\\"");

            builder.AppendLine($"{indentationString}public class {icon.Name} : Icon {{ public {icon.Name}() : base(\"{icon.Name}\", IconVariant.{icon.Variant}, IconSize.Size{icon.Size}, \"{svgContent}\") {{ }} }}");
        }
    }

    [GeneratedRegex("^(Filled|Regular)[0-9][0-9](\\.cs|\\.resx)$")]
    private static partial Regex NameParser();
}